// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.embedding.android;

import static io.flutter.Build.API_LEVELS;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_URI_META_DATA_KEY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
import io.flutter.plugin.platform.PlatformPlugin;
import java.util.ArrayList;
import java.util.List;

/**
 * {@code Activity} which displays a fullscreen Flutter UI.
 *
 * <p>{@code FlutterActivity} is the simplest and most direct way to integrate Flutter within an
 * Android app.
 *
 * <p><strong>FlutterActivity responsibilities</strong>
 *
 * <p>{@code FlutterActivity} maintains the following responsibilities:
 *
 * <ul>
 *   <li>Displays an Android launch screen.
 *   <li>Configures the status bar appearance.
 *   <li>Chooses the Dart execution app bundle path, entrypoint and entrypoint arguments.
 *   <li>Chooses Flutter's initial route.
 *   <li>Renders {@code Activity} transparently, if desired.
 *   <li>Offers hooks for subclasses to provide and configure a {@link
 *       io.flutter.embedding.engine.FlutterEngine}.
 *   <li>Save and restore instance state, see {@code #shouldRestoreAndSaveState()};
 * </ul>
 *
 * <p><strong>Dart entrypoint, entrypoint arguments, initial route, and app bundle path</strong>
 *
 * <p>The Dart entrypoint executed within this {@code Activity} is "main()" by default. To change
 * the entrypoint that a {@code FlutterActivity} executes, subclass {@code FlutterActivity} and
 * override {@link #getDartEntrypointFunctionName()}. For non-main Dart entrypoints to not be
 * tree-shaken away, you need to annotate those functions with {@code @pragma('vm:entry-point')} in
 * Dart.
 *
 * <p>The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint
 * function. It can be passed using a {@link NewEngineIntentBuilder} via {@link
 * NewEngineIntentBuilder#dartEntrypointArgs}.
 *
 * <p>The Flutter route that is initially loaded within this {@code Activity} is "/". The initial
 * route may be specified explicitly by passing the name of the route as a {@code String} in {@link
 * FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE}, e.g., "my/deep/link".
 *
 * <p>The initial route can each be controlled using a {@link NewEngineIntentBuilder} via {@link
 * NewEngineIntentBuilder#initialRoute}.
 *
 * <p>The app bundle path, Dart entrypoint, Dart entrypoint arguments, and initial route can also be
 * controlled in a subclass of {@code FlutterActivity} by overriding their respective methods:
 *
 * <ul>
 *   <li>{@link #getAppBundlePath()}
 *   <li>{@link #getDartEntrypointFunctionName()}
 *   <li>{@link #getDartEntrypointArgs()}
 *   <li>{@link #getInitialRoute()}
 * </ul>
 *
 * <p>The Dart entrypoint and app bundle path are not supported as {@code Intent} parameters since
 * your Dart library entrypoints are your private APIs and Intents are invocable by other processes.
 *
 * <p><strong>Using a cached FlutterEngine</strong>
 *
 * <p>{@code FlutterActivity} can be used with a cached {@link
 * io.flutter.embedding.engine.FlutterEngine} instead of creating a new one. Use {@link
 * #withCachedEngine(String)} to build a {@code FlutterActivity} {@code Intent} that is configured
 * to use an existing, cached {@link io.flutter.embedding.engine.FlutterEngine}. {@link
 * io.flutter.embedding.engine.FlutterEngineCache} is the cache that is used to obtain a given
 * cached {@link io.flutter.embedding.engine.FlutterEngine}. You must create and put a {@link
 * io.flutter.embedding.engine.FlutterEngine} into the {@link
 * io.flutter.embedding.engine.FlutterEngineCache} yourself before using the {@link
 * #withCachedEngine(String)} builder. An {@code IllegalStateException} will be thrown if a cached
 * engine is requested but does not exist in the cache.
 *
 * <p>When using a cached {@link io.flutter.embedding.engine.FlutterEngine}, that {@link
 * io.flutter.embedding.engine.FlutterEngine} should already be executing Dart code, which means
 * that the Dart entrypoint and initial route have already been defined. Therefore, {@link
 * CachedEngineIntentBuilder} does not offer configuration of these properties.
 *
 * <p>It is generally recommended to use a cached {@link io.flutter.embedding.engine.FlutterEngine}
 * to avoid a momentary delay when initializing a new {@link
 * io.flutter.embedding.engine.FlutterEngine}. The two exceptions to using a cached {@link
 * FlutterEngine} are:
 *
 * <ul>
 *   <li>When {@code FlutterActivity} is the first {@code Activity} displayed by the app, because
 *       pre-warming a {@link io.flutter.embedding.engine.FlutterEngine} would have no impact in
 *       this situation.
 *   <li>When you are unsure when/if you will need to display a Flutter experience.
 * </ul>
 *
 * <p>See https://flutter.dev/docs/development/add-to-app/performance for additional performance
 * explorations on engine loading.
 *
 * <p>The following illustrates how to pre-warm and cache a {@link
 * io.flutter.embedding.engine.FlutterEngine}:
 *
 * <pre>{@code
 * // Create and pre-warm a FlutterEngine.
 * FlutterEngineGroup group = new FlutterEngineGroup(context);
 * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
 * flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault());
 *
 * // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.
 * FlutterEngineCache.getInstance().put("my_engine", flutterEngine);
 * }</pre>
 *
 * <p><strong>Alternatives to FlutterActivity</strong>
 *
 * <p>If Flutter is needed in a location that cannot use an {@code Activity}, consider using a
 * {@link FlutterFragment}. Using a {@link FlutterFragment} requires forwarding some calls from an
 * {@code Activity} to the {@link FlutterFragment}.
 *
 * <p>If Flutter is needed in a location that can only use a {@code View}, consider using a {@link
 * FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an {@code
 * Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a {@code Fragment}.
 *
 * <p><strong>Launch Screen</strong>
 *
 * <p>{@code FlutterActivity} supports the display of an Android "launch screen", which is displayed
 * while the Android application loads. It is only applicable if {@code FlutterActivity} is the
 * first {@code Activity} displayed upon loading the app.
 *
 * <p>Prior to Flutter 2.5, {@code FlutterActivity} supported the display of a Flutter-specific
 * "splash screen" that would be displayed after the launch screen passes. This has since been
 * deprecated. If a launch screen is specified, it will automatically persist for as long as it
 * takes Flutter to initialize and render its first frame.
 *
 * <p>Use Android themes to display a launch screen. Create two themes: a launch theme and a normal
 * theme. In the launch theme, set {@code windowBackground} to the desired {@code Drawable} for the
 * launch screen. In the normal theme, set {@code windowBackground} to any desired background color
 * that should normally appear behind your Flutter content. In most cases this background color will
 * never be seen, but for possible transition edge cases it is a good idea to explicitly replace the
 * launch screen window background with a neutral color.
 *
 * <p>Do not change aspects of system chrome between a launch theme and normal theme. Either define
 * both themes to be fullscreen or not, and define both themes to display the same status bar and
 * navigation bar settings. To adjust system chrome once the Flutter app renders, use platform
 * channels to instruct Android to do so at the appropriate time. This will avoid any jarring visual
 * changes during app startup.
 *
 * <p>In the AndroidManifest.xml, set the theme of {@code FlutterActivity} to the defined launch
 * theme. In the metadata section for {@code FlutterActivity}, defined the following reference to
 * your normal theme:
 *
 * <p>{@code <meta-data android:name="io.flutter.embedding.android.NormalTheme"
 * android:resource="@style/YourNormalTheme" /> }
 *
 * <p>With themes defined, and AndroidManifest.xml updated, Flutter displays the specified launch
 * screen until the Android application is initialized.
 *
 * <p><strong>Alternative Activity</strong> {@link FlutterFragmentActivity} is also available, which
 * is similar to {@code FlutterActivity} but it extends {@code FragmentActivity}. You should use
 * {@code FlutterActivity}, if possible, but if you need a {@code FragmentActivity} then you should
 * use {@link FlutterFragmentActivity}.
 */
// A number of methods in this class have the same implementation as FlutterFragmentActivity. These
// methods are duplicated for readability purposes. Be sure to replicate any change in this class in
// FlutterFragmentActivity, too.
//搜索 "FragmentActivity 替换Activity 修改"  看相关修改
public class FlutterActivity2 extends FragmentActivity
        implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner {
   private static final String TAG = "FlutterActivity";

   private boolean hasRegisteredBackCallback = false;

   /**
    * The ID of the {@code FlutterView} created by this activity.
    *
    * <p>This ID can be used to lookup {@code FlutterView} in the Android view hierarchy. For more,
    * see {@link android.view.View#findViewById}.
    */
   public static final int FLUTTER_VIEW_ID = View.generateViewId();

   /**
    * Creates an {@link Intent} that launches a {@code FlutterActivity}, which creates a {@link
    * FlutterEngine} that executes a {@code main()} Dart entrypoint, and displays the "/" route as
    * Flutter's initial route.
    *
    * <p>Consider using the {@link #withCachedEngine(String)} {@link Intent} builder to control when
    * the {@link io.flutter.embedding.engine.FlutterEngine} should be created in your application.
    *
    * @param launchContext The launch context. e.g. An Activity.
    * @return The default intent.
    */
   @NonNull
   public static Intent createDefaultIntent(@NonNull Context launchContext) {
      return withNewEngine().build(launchContext);
   }

   /**
    * Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
    * launch a {@code FlutterActivity} that internally creates a new {@link
    * io.flutter.embedding.engine.FlutterEngine} using the desired Dart entrypoint, initial route,
    * etc.
    *
    * @return The engine intent builder.
    */
   @NonNull
   public static NewEngineIntentBuilder withNewEngine() {
      return new NewEngineIntentBuilder(FlutterActivity2.class);
   }

   /**
    * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new {@link
    * FlutterEngine} and the desired configuration.
    */
   public static class NewEngineIntentBuilder {
      private final Class<? extends FlutterActivity2> activityClass;
      private String initialRoute = DEFAULT_INITIAL_ROUTE;
      private String backgroundMode = DEFAULT_BACKGROUND_MODE;
      @Nullable private List<String> dartEntrypointArgs;

      /**
       * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
       * {@code FlutterActivity}.
       *
       * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
       * #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder} constructed
       * with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
       *
       * <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }
       */
      public NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity2> activityClass) {
         this.activityClass = activityClass;
      }

      /**
       * The initial route that a Flutter app will render in this {@link FlutterActivity2}, defaults to
       * "/".
       *
       * @param initialRoute The route.
       * @return The engine intent builder.
       */
      @NonNull
      public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
         this.initialRoute = initialRoute;
         return this;
      }

      /**
       * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
       * {@link BackgroundMode#transparent}.
       *
       * <p>The default background mode is {@link BackgroundMode#opaque}.
       *
       * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
       * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
       * FlutterTextureView} to support transparency. This choice has a non-trivial performance
       * impact. A transparent background should only be used if it is necessary for the app design
       * being implemented.
       *
       * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
       * BackgroundMode#transparent} must have a theme applied to it that includes the following
       * property: {@code <item name="android:windowIsTranslucent">true</item>}.
       *
       * @param backgroundMode The background mode.
       * @return The engine intent builder.
       */
      @NonNull
      public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
         this.backgroundMode = backgroundMode.name();
         return this;
      }

      /**
       * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint
       * function.
       *
       * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
       *
       * @param dartEntrypointArgs The Dart entrypoint arguments.
       * @return The engine intent builder.
       */
      @NonNull
      public NewEngineIntentBuilder dartEntrypointArgs(@Nullable List<String> dartEntrypointArgs) {
         this.dartEntrypointArgs = dartEntrypointArgs;
         return this;
      }

      /**
       * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
       * desired configuration.
       *
       * @param context The context. e.g. An Activity.
       * @return The intent.
       */
      @NonNull
      public Intent build(@NonNull Context context) {
         Intent intent =
                 new Intent(context, activityClass)
                         .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
                         .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
                         .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
         if (dartEntrypointArgs != null) {
            intent.putExtra(EXTRA_DART_ENTRYPOINT_ARGS, new ArrayList(dartEntrypointArgs));
         }
         return intent;
      }
   }

   /**
    * Creates a {@link CachedEngineIntentBuilder}, which can be used to configure an {@link Intent}
    * to launch a {@code FlutterActivity} that internally uses an existing {@link
    * io.flutter.embedding.engine.FlutterEngine} that is cached in {@link
    * io.flutter.embedding.engine.FlutterEngineCache}.
    *
    * @param cachedEngineId A cached engine ID.
    * @return The builder.
    */
   public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
      return new CachedEngineIntentBuilder(FlutterActivity2.class, cachedEngineId);
   }

   /**
    * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with an existing
    * {@link io.flutter.embedding.engine.FlutterEngine} that is cached in {@link
    * io.flutter.embedding.engine.FlutterEngineCache}.
    */
   public static class CachedEngineIntentBuilder {
      private final Class<? extends FlutterActivity2> activityClass;
      private final String cachedEngineId;
      private boolean destroyEngineWithActivity = false;
      private String backgroundMode = DEFAULT_BACKGROUND_MODE;

      /**
       * Constructor that allows this {@code CachedEngineIntentBuilder} to be used by subclasses of
       * {@code FlutterActivity}.
       *
       * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
       * FlutterActivity2#withCachedEngine(String)}, which returns an instance of {@code
       * CachedEngineIntentBuilder} constructed with a {@code Class} reference to the {@code
       * FlutterActivity} subclass, e.g.:
       *
       * <p>{@code return new CachedEngineIntentBuilder(MyFlutterActivity.class, engineId); }
       *
       * @param activityClass A subclass of {@code FlutterActivity}.
       * @param engineId The engine id.
       */
      public CachedEngineIntentBuilder(
              @NonNull Class<? extends FlutterActivity2> activityClass, @NonNull String engineId) {
         this.activityClass = activityClass;
         this.cachedEngineId = engineId;
      }

      /**
       * Whether the cached {@link io.flutter.embedding.engine.FlutterEngine} should be destroyed and
       * removed from the cache when this {@code FlutterActivity} is destroyed.
       *
       * <p>The default value is {@code false}.
       *
       * @param destroyEngineWithActivity Whether to destroy the engine.
       * @return The builder.
       */
      public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
         this.destroyEngineWithActivity = destroyEngineWithActivity;
         return this;
      }

      /**
       * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
       * {@link BackgroundMode#transparent}.
       *
       * <p>The default background mode is {@link BackgroundMode#opaque}.
       *
       * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
       * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
       * FlutterTextureView} to support transparency. This choice has a non-trivial performance
       * impact. A transparent background should only be used if it is necessary for the app design
       * being implemented.
       *
       * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
       * BackgroundMode#transparent} must have a theme applied to it that includes the following
       * property: {@code <item name="android:windowIsTranslucent">true</item>}.
       *
       * @param backgroundMode The background mode
       * @return The builder.
       */
      @NonNull
      public CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
         this.backgroundMode = backgroundMode.name();
         return this;
      }

      /**
       * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
       * desired configuration.
       *
       * @param context The context. e.g. An Activity.
       * @return The intent.
       */
      @NonNull
      public Intent build(@NonNull Context context) {
         return new Intent(context, activityClass)
                 .putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
                 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
                 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
      }
   }

   /**
    * Creates a {@link NewEngineInGroupIntentBuilder}, which can be used to configure an {@link
    * Intent} to launch a {@code FlutterActivity} by internally creating a FlutterEngine from an
    * existing {@link io.flutter.embedding.engine.FlutterEngineGroup} cached in a specified {@link
    * io.flutter.embedding.engine.FlutterEngineGroupCache}.
    *
    * <pre>{@code
    * // Create a FlutterEngineGroup, such as in the onCreate method of the Application.
    * FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
    * FlutterEngineGroupCache.getInstance().put("my_cached_engine_group_id", engineGroup);
    *
    * // Start a FlutterActivity with the FlutterEngineGroup by creating an intent with withNewEngineInGroup
    * Intent intent = FlutterActivity.withNewEngineInGroup("my_cached_engine_group_id")
    *     .dartEntrypoint("custom_entrypoint")
    *     .initialRoute("/custom/route")
    *     .backgroundMode(BackgroundMode.transparent)
    *     .build(context);
    * startActivity(intent);
    * }</pre>
    *
    * @param engineGroupId A cached engine group ID.
    * @return The builder.
    */
   public static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId) {
      return new NewEngineInGroupIntentBuilder(FlutterActivity2.class, engineGroupId);
   }

   /**
    * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new {@link
    * FlutterEngine} created by FlutterEngineGroup#createAndRunEngine.
    */
   public static class NewEngineInGroupIntentBuilder {
      private final Class<? extends FlutterActivity2> activityClass;
      private final String cachedEngineGroupId;
      private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
      private String initialRoute = DEFAULT_INITIAL_ROUTE;
      private String backgroundMode = DEFAULT_BACKGROUND_MODE;

      /**
       * Constructor that allows this {@code NewEngineInGroupIntentBuilder} to be used by subclasses
       * of {@code FlutterActivity}.
       *
       * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
       * #withNewEngineInGroup}, which returns an instance of {@code NewEngineInGroupIntentBuilder}
       * constructed with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
       *
       * <p>{@code return new NewEngineInGroupIntentBuilder(MyFlutterActivity.class,
       * cacheedEngineGroupId); }
       *
       * <pre>{@code
       * // Create a FlutterEngineGroup, such as in the onCreate method of the Application.
       * FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
       * FlutterEngineGroupCache.getInstance().put("my_cached_engine_group_id", engineGroup);
       *
       * // Create a NewEngineInGroupIntentBuilder that would build an intent to start my custom FlutterActivity subclass.
       * FlutterActivity.NewEngineInGroupIntentBuilder intentBuilder =
       *     new FlutterActivity.NewEngineInGroupIntentBuilder(
       *           MyFlutterActivity.class,
       *           app.engineGroupId);
       * intentBuilder.dartEntrypoint("main")
       *     .initialRoute("/custom/route")
       *     .backgroundMode(BackgroundMode.transparent);
       * startActivity(intentBuilder.build(context));
       * }</pre>
       *
       * @param activityClass A subclass of {@code FlutterActivity}.
       * @param engineGroupId The engine group id.
       */
      public NewEngineInGroupIntentBuilder(
              @NonNull Class<? extends FlutterActivity2> activityClass, @NonNull String engineGroupId) {
         this.activityClass = activityClass;
         this.cachedEngineGroupId = engineGroupId;
      }

      /**
       * The Dart entrypoint that will be executed in the newly created FlutterEngine as soon as the
       * Dart snapshot is loaded. Default to "main".
       *
       * @param dartEntrypoint The dart entrypoint's name
       * @return The engine group intent builder
       */
      @NonNull
      public NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
         this.dartEntrypoint = dartEntrypoint;
         return this;
      }

      /**
       * The initial route that a Flutter app will render in this {@link FlutterActivity2}, defaults to
       * "/".
       *
       * @param initialRoute The route.
       * @return The engine group intent builder.
       */
      @NonNull
      public NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute) {
         this.initialRoute = initialRoute;
         return this;
      }

      /**
       * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
       * {@link BackgroundMode#transparent}.
       *
       * <p>The default background mode is {@link BackgroundMode#opaque}.
       *
       * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
       * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
       * FlutterTextureView} to support transparency. This choice has a non-trivial performance
       * impact. A transparent background should only be used if it is necessary for the app design
       * being implemented.
       *
       * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
       * BackgroundMode#transparent} must have a theme applied to it that includes the following
       * property: {@code <item name="android:windowIsTranslucent">true</item>}.
       *
       * @param backgroundMode The background mode.
       * @return The engine group intent builder.
       */
      @NonNull
      public NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
         this.backgroundMode = backgroundMode.name();
         return this;
      }

      /**
       * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
       * desired configuration.
       *
       * @param context The context. e.g. An Activity.
       * @return The intent.
       */
      @NonNull
      public Intent build(@NonNull Context context) {
         return new Intent(context, activityClass)
                 .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
                 .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
                 .putExtra(EXTRA_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId)
                 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
                 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
      }
   }

   // Delegate that runs all lifecycle and OS hook logic that is common between
   // FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
   // implementation for details about why it exists.
   @VisibleForTesting protected FlutterActivityAndFragmentDelegate delegate;

//FragmentActivity 替换Activity 修改
   //   @NonNull private LifecycleRegistry lifecycle;
//  直接套用fragmentActivity的了
   public FlutterActivity2() {
      super();
      //FragmentActivity 替换Activity 修改
//      lifecycle = new LifecycleRegistry(this);
   }

   /**
    * This method exists so that JVM tests can ensure that a delegate exists without putting this
    * Activity through any lifecycle events, because JVM tests cannot handle executing any lifecycle
    * methods, at the time of writing this.
    *
    * <p>The testing infrastructure should be upgraded to make FlutterActivity tests easy to write
    * while exercising real lifecycle methods. At such a time, this method should be removed.
    *
    * @param delegate The delegate.
    */
   // TODO(mattcarroll): remove this when tests allow for it
   // (https://github.com/flutter/flutter/issues/43798)
   @VisibleForTesting
   /* package */ void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate) {
      this.delegate = delegate;
   }

   /**
    * Returns the Android App Component exclusively attached to {@link
    * io.flutter.embedding.engine.FlutterEngine}.
    */
   @Override
   public ExclusiveAppComponent<Activity> getExclusiveAppComponent() {
      return delegate;
   }

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
      switchLaunchThemeForNormalTheme();

      super.onCreate(savedInstanceState);

      delegate = new FlutterActivityAndFragmentDelegate(this);
      delegate.onAttach(this);
      delegate.onRestoreInstanceState(savedInstanceState);
//FragmentActivity 替换Activity 修改
//      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);

      configureWindowForTransparency();

      setContentView(createFlutterView());

      configureStatusBarForFullscreenFlutterExperience();
   }

   /**
    * Registers the callback with OnBackInvokedDispatcher to capture back navigation gestures and
    * pass them to the framework.
    *
    * <p>This replaces the deprecated onBackPressed method override in order to support API 33's
    * predictive back navigation feature.
    *
    * <p>The callback must be unregistered in order to prevent unpredictable behavior once outside
    * the Flutter app.
    */
   @VisibleForTesting
   public void registerOnBackInvokedCallback() {
      if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
         getOnBackInvokedDispatcher()
                 .registerOnBackInvokedCallback(
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
         hasRegisteredBackCallback = true;
      }
   }

   /**
    * Unregisters the callback from OnBackInvokedDispatcher.
    *
    * <p>This should be called when the activity is no longer in use to prevent unpredictable
    * behavior such as being stuck and unable to press back.
    */
   @VisibleForTesting
   public void unregisterOnBackInvokedCallback() {
      if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
         getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
         hasRegisteredBackCallback = false;
      }
   }

   private final OnBackInvokedCallback onBackInvokedCallback =
           Build.VERSION.SDK_INT < API_LEVELS.API_33 ? null : createOnBackInvokedCallback();

   @VisibleForTesting
   protected OnBackInvokedCallback getOnBackInvokedCallback() {
      return onBackInvokedCallback;
   }

   @NonNull
   @TargetApi(API_LEVELS.API_33)
   @RequiresApi(API_LEVELS.API_33)
   private OnBackInvokedCallback createOnBackInvokedCallback() {
      if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) {
         return new OnBackAnimationCallback() {
            @Override
            public void onBackInvoked() {
               commitBackGesture();
            }

            @Override
            public void onBackCancelled() {
               cancelBackGesture();
            }

            @Override
            public void onBackProgressed(@NonNull BackEvent backEvent) {
               updateBackGestureProgress(backEvent);
            }

            @Override
            public void onBackStarted(@NonNull BackEvent backEvent) {
               startBackGesture(backEvent);
            }
         };
      }

      return this::onBackPressed;
   }

   @Override
   public void setFrameworkHandlesBack(boolean frameworkHandlesBack) {
      if (frameworkHandlesBack && !hasRegisteredBackCallback) {
         registerOnBackInvokedCallback();
      } else if (!frameworkHandlesBack && hasRegisteredBackCallback) {
         unregisterOnBackInvokedCallback();
      }
   }

   /**
    * Switches themes for this {@code Activity} from the theme used to launch this {@code Activity}
    * to a "normal theme" that is intended for regular {@code Activity} operation.
    *
    * <p>This behavior is offered so that a "launch screen" can be displayed while the application
    * initially loads. To utilize this behavior in an app, do the following:
    *
    * <ol>
    *   <li>Create 2 different themes in style.xml: one theme for the launch screen and one theme for
    *       normal display.
    *   <li>In the launch screen theme, set the "windowBackground" property to a {@code Drawable} of
    *       your choice.
    *   <li>In the normal theme, customize however you'd like.
    *   <li>In the AndroidManifest.xml, set the theme of your {@code FlutterActivity} to your launch
    *       theme.
    *   <li>Add a {@code <meta-data>} property to your {@code FlutterActivity} with a name of
    *       "io.flutter.embedding.android.NormalTheme" and set the resource to your normal theme,
    *       e.g., {@code android:resource="@style/MyNormalTheme}.
    * </ol>
    *
    * With the above settings, your launch theme will be used when loading the app, and then the
    * theme will be switched to your normal theme once the app has initialized.
    *
    * <p>Do not change aspects of system chrome between a launch theme and normal theme. Either
    * define both themes to be fullscreen or not, and define both themes to display the same status
    * bar and navigation bar settings. If you wish to adjust system chrome once your Flutter app
    * renders, use platform channels to instruct Android to do so at the appropriate time. This will
    * avoid any jarring visual changes during app startup.
    */
   private void switchLaunchThemeForNormalTheme() {
      try {
         Bundle metaData = getMetaData();
         if (metaData != null) {
            int normalThemeRID = metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
            if (normalThemeRID != -1) {
               setTheme(normalThemeRID);
            }
         } else {
            Log.v(TAG, "Using the launch theme as normal theme.");
         }
      } catch (PackageManager.NameNotFoundException exception) {
         Log.e(
                 TAG,
                 "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme.");
      }
   }

   /**
    * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
    * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link
    * BackgroundMode#transparent}.
    *
    * <p>For {@code Activity} transparency to work as expected, the theme applied to this {@code
    * Activity} must include {@code <item name="android:windowIsTranslucent">true</item>}.
    */
   private void configureWindowForTransparency() {
      BackgroundMode backgroundMode = getBackgroundMode();
      if (backgroundMode == BackgroundMode.transparent) {
         getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
      }
   }

   @NonNull
   private View createFlutterView() {
      return delegate.onCreateView(
              /* inflater=*/ null,
              /* container=*/ null,
              /* savedInstanceState=*/ null,
              /*flutterViewId=*/ FLUTTER_VIEW_ID,
              /*shouldDelayFirstAndroidViewDraw=*/ getRenderMode() == RenderMode.surface);
   }

   private void configureStatusBarForFullscreenFlutterExperience() {
      Window window = getWindow();
      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          window.setStatusBarColor(0x40000000);
          window.setNavigationBarColor(0x00000000);
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
               window.setNavigationBarDividerColor(0x00000000);
           }
       }
      window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
   }

   @Override
   protected void onStart() {
      super.onStart();
      //FragmentActivity 替换Activity 修改
//      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
      if (stillAttachedForEvent("onStart")) {
         delegate.onStart();
      }
   }

   @Override
   protected void onResume() {
      super.onResume();
      //FragmentActivity 替换Activity 修改
//      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
      if (stillAttachedForEvent("onResume")) {
         delegate.onResume();
      }
   }

   @Override
   public void onPostResume() {
      super.onPostResume();
      if (stillAttachedForEvent("onPostResume")) {
         delegate.onPostResume();
      }
   }

   @Override
   protected void onPause() {
      super.onPause();
      if (stillAttachedForEvent("onPause")) {
         delegate.onPause();
      }
      //FragmentActivity 替换Activity 修改
//      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
   }

   @Override
   protected void onStop() {
      super.onStop();
      if (stillAttachedForEvent("onStop")) {
         delegate.onStop();
      }
      //FragmentActivity 替换Activity 修改
//      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
   }

   @Override
   protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      if (stillAttachedForEvent("onSaveInstanceState")) {
         delegate.onSaveInstanceState(outState);
      }
   }

   /**
    * Irreversibly release this activity's control of the {@link
    * io.flutter.embedding.engine.FlutterEngine} and its subcomponents.
    *
    * <p>Calling will disconnect this activity's view from the Flutter renderer, disconnect this
    * activity from plugins' {@link ActivityControlSurface}, and stop system channel messages from
    * this activity.
    *
    * <p>After calling, this activity should be disposed immediately and not be re-used.
    */
   @VisibleForTesting
   public void release() {
      unregisterOnBackInvokedCallback();
      if (delegate != null) {
         delegate.release();
         delegate = null;
      }
   }

   @Override
   public void detachFromFlutterEngine() {
      Log.w(
              TAG,
              "FlutterActivity "
                      + this
                      + " connection to the engine "
                      + getFlutterEngine()
                      + " evicted by another attaching activity");
      if (delegate != null) {
         delegate.onDestroyView();
         delegate.onDetach();
      }
   }

   @Override
   protected void onDestroy() {
      super.onDestroy();
      if (stillAttachedForEvent("onDestroy")) {
         delegate.onDestroyView();
         delegate.onDetach();
      }
      release();
      //FragmentActivity 替换Activity 修改
//      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
   }

   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (stillAttachedForEvent("onActivityResult")) {
         delegate.onActivityResult(requestCode, resultCode, data);
      }
   }

   @Override
   protected void onNewIntent(@NonNull Intent intent) {
      // TODO(mattcarroll): change G3 lint rule that forces us to call super
      super.onNewIntent(intent);
      if (stillAttachedForEvent("onNewIntent")) {
         delegate.onNewIntent(intent);
      }
   }

   @Override
   public void onBackPressed() {
      if (stillAttachedForEvent("onBackPressed")) {
         delegate.onBackPressed();
      }
   }

   @TargetApi(API_LEVELS.API_34)
   @RequiresApi(API_LEVELS.API_34)
   public void startBackGesture(@NonNull BackEvent backEvent) {
      if (stillAttachedForEvent("startBackGesture")) {
         delegate.startBackGesture(backEvent);
      }
   }

   @TargetApi(API_LEVELS.API_34)
   @RequiresApi(API_LEVELS.API_34)
   public void updateBackGestureProgress(@NonNull BackEvent backEvent) {
      if (stillAttachedForEvent("updateBackGestureProgress")) {
         delegate.updateBackGestureProgress(backEvent);
      }
   }

   @TargetApi(API_LEVELS.API_34)
   @RequiresApi(API_LEVELS.API_34)
   public void commitBackGesture() {
      if (stillAttachedForEvent("commitBackGesture")) {
         delegate.commitBackGesture();
      }
   }

   @TargetApi(API_LEVELS.API_34)
   @RequiresApi(API_LEVELS.API_34)
   public void cancelBackGesture() {
      if (stillAttachedForEvent("cancelBackGesture")) {
         delegate.cancelBackGesture();
      }
   }

   @Override
   public void onRequestPermissionsResult(
           int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
      if (stillAttachedForEvent("onRequestPermissionsResult")) {
         delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
      }
   }

   @Override
   public void onUserLeaveHint() {
      if (stillAttachedForEvent("onUserLeaveHint")) {
         delegate.onUserLeaveHint();
      }
   }

   @Override
   public void onWindowFocusChanged(boolean hasFocus) {
      super.onWindowFocusChanged(hasFocus);
      if (stillAttachedForEvent("onWindowFocusChanged")) {
         delegate.onWindowFocusChanged(hasFocus);
      }
   }

   @Override
   public void onTrimMemory(int level) {
      super.onTrimMemory(level);
      if (stillAttachedForEvent("onTrimMemory")) {
         delegate.onTrimMemory(level);
      }
   }

   /**
    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
    * FlutterActivityAndFragmentDelegate} to obtain a {@code Context} reference as needed.
    */
   @Override
   @NonNull
   public Context getContext() {
      return this;
   }

   /**
    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
    * FlutterActivityAndFragmentDelegate} to obtain an {@code Activity} reference as needed. This
    * reference is used by the delegate to instantiate a {@link FlutterView}, a {@link
    * PlatformPlugin}, and to determine if the {@code Activity} is changing configurations.
    */
   @Override
   @NonNull
   public Activity getActivity() {
      return this;
   }

   /**
    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
    * FlutterActivityAndFragmentDelegate} to obtain a {@code Lifecycle} reference as needed. This
    * reference is used by the delegate to provide Flutter plugins with access to lifecycle events.
    */
   //FragmentActivity 替换Activity 修改
//   这里继承FragmentActivity  就直接不用复写了，用FragmentActivity的getLifecycle即可
//   @Override
//   @NonNull
//   public Lifecycle getLifecycle() {
//      return lifecycle;
//   }

   /**
    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
    * FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when initializing
    * Flutter.
    */
   @NonNull
   @Override
   public FlutterShellArgs getFlutterShellArgs() {
      return FlutterShellArgs.fromIntent(getIntent());
   }

   /**
    * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to use
    * within this {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does not
    * want to use a cached {@link io.flutter.embedding.engine.FlutterEngine}.
    */
   @Override
   @Nullable
   public String getCachedEngineId() {
      return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
   }

   /**
    * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngineGroup} to
    * use within this {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does
    * not want to use a cached {@link io.flutter.embedding.engine.FlutterEngineGroup}.
    */
   @Override
   @Nullable
   public String getCachedEngineGroupId() {
      return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID);
   }

   /**
    * Returns false if the {@link io.flutter.embedding.engine.FlutterEngine} backing this {@code
    * FlutterActivity} should outlive this {@code FlutterActivity}, or true to be destroyed when the
    * {@code FlutterActivity} is destroyed.
    *
    * <p>The default value is {@code true} in cases where {@code FlutterActivity} created its own
    * {@link io.flutter.embedding.engine.FlutterEngine}, and {@code false} in cases where a cached
    * {@link io.flutter.embedding.engine.FlutterEngine} was provided.
    */
   @Override
   public boolean shouldDestroyEngineWithHost() {
      boolean explicitDestructionRequested =
              getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
      if (getCachedEngineId() != null || delegate.isFlutterEngineFromHost()) {
         // Only destroy a cached engine if explicitly requested by app developer.
         return explicitDestructionRequested;
      } else {
         // If this Activity created the FlutterEngine, destroy it by default unless
         // explicitly requested not to.
         return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
      }
   }

   /**
    * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
    *
    * <p>This preference can be controlled with 2 methods:
    *
    * <ol>
    *   <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_DART_ENTRYPOINT} with the
    *       launching {@code Intent}, or
    *   <li>Set a {@code <meta-data>} called {@link
    *       FlutterActivityLaunchConfigs#DART_ENTRYPOINT_META_DATA_KEY} within the Android manifest
    *       definition for this {@code FlutterActivity}
    * </ol>
    *
    * If both preferences are set, the {@code Intent} preference takes priority.
    *
    * <p>Subclasses may override this method to directly control the Dart entrypoint.
    */
   @NonNull
   public String getDartEntrypointFunctionName() {
      if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) {
         return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT);
      }

      try {
         Bundle metaData = getMetaData();
         String desiredDartEntrypoint =
                 metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
         return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
      } catch (PackageManager.NameNotFoundException e) {
         return DEFAULT_DART_ENTRYPOINT;
      }
   }

   /**
    * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint function.
    *
    * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
    *
    * <p>Subclasses may override this method to directly control the Dart entrypoint arguments.
    */
   @Nullable
   public List<String> getDartEntrypointArgs() {
      return (List<String>) getIntent().getSerializableExtra(EXTRA_DART_ENTRYPOINT_ARGS);
   }

   /**
    * The Dart library URI for the entrypoint that will be executed as soon as the Dart snapshot is
    * loaded.
    *
    * <p>Example value: "package:foo/bar.dart"
    *
    * <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
    * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_URI_META_DATA_KEY} within the Android manifest
    * definition for this {@code FlutterActivity}.
    *
    * <p>A value of null means use the default root library.
    *
    * <p>Subclasses may override this method to directly control the Dart entrypoint uri.
    */
   @Nullable
   public String getDartEntrypointLibraryUri() {
      try {
         Bundle metaData = getMetaData();
         String desiredDartLibraryUri =
                 metaData != null ? metaData.getString(DART_ENTRYPOINT_URI_META_DATA_KEY) : null;
         return desiredDartLibraryUri;
      } catch (PackageManager.NameNotFoundException e) {
         return null;
      }
   }

   /**
    * The initial route that a Flutter app will render upon loading and executing its Dart code.
    *
    * <p>This preference can be controlled with 2 methods:
    *
    * <ol>
    *   <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE} with the
    *       launching {@code Intent}, or
    *   <li>Set a {@code <meta-data>} called {@link
    *       FlutterActivityLaunchConfigs#INITIAL_ROUTE_META_DATA_KEY} for this {@code Activity} in
    *       the Android manifest.
    * </ol>
    *
    * If both preferences are set, the {@code Intent} preference takes priority.
    *
    * <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
    * Activity} might be the very first {@code Activity} launched, which means the developer won't
    * have control over the incoming {@code Intent}.
    *
    * <p>Subclasses may override this method to directly control the initial route.
    *
    * <p>If this method returns null and the {@code shouldHandleDeeplinking} returns true, the
    * initial route is derived from the {@code Intent} through the Intent.getData() instead.
    */
   public String getInitialRoute() {
      if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
         return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
      }

      try {
         Bundle metaData = getMetaData();
         String desiredInitialRoute =
                 metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
         return desiredInitialRoute;
      } catch (PackageManager.NameNotFoundException e) {
         return null;
      }
   }

   /**
    * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code
    * snapshots.
    *
    * <p>When this {@code FlutterActivity} is run by Flutter tooling and a data String is included in
    * the launching {@code Intent}, that data String is interpreted as an app bundle path.
    *
    * <p>When otherwise unspecified, the value is null, which defaults to the app bundle path defined
    * in {@link io.flutter.embedding.engine.loader.FlutterLoader#findAppBundlePath()}.
    *
    * <p>Subclasses may override this method to return a custom app bundle path.
    */
   @NonNull
   public String getAppBundlePath() {
      // If this Activity was launched from tooling, and the incoming Intent contains
      // a custom app bundle path, return that path.
      // TODO(mattcarroll): determine if we should have an explicit FlutterTestActivity instead of
      // conflating.
      if (isDebuggable() && Intent.ACTION_RUN.equals(getIntent().getAction())) {
         String appBundlePath = getIntent().getDataString();
         if (appBundlePath != null) {
            return appBundlePath;
         }
      }

      return null;
   }

   /**
    * Returns true if Flutter is running in "debug mode", and false otherwise.
    *
    * <p>Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
    */
   private boolean isDebuggable() {
      return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
   }

   /**
    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
    * FlutterActivityAndFragmentDelegate} to obtain the desired {@link RenderMode} that should be
    * used when instantiating a {@link FlutterView}.
    */
   @NonNull
   @Override
   public RenderMode getRenderMode() {
      return getBackgroundMode() == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture;
   }

   /**
    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
    * FlutterActivityAndFragmentDelegate} to obtain the desired {@link TransparencyMode} that should
    * be used when instantiating a {@link FlutterView}.
    */
   @NonNull
   @Override
   public TransparencyMode getTransparencyMode() {
      return getBackgroundMode() == BackgroundMode.opaque
              ? TransparencyMode.opaque
              : TransparencyMode.transparent;
   }

   /**
    * The desired window background mode of this {@code Activity}, which defaults to {@link
    * BackgroundMode#opaque}.
    *
    * @return The background mode.
    */
   @NonNull
   protected BackgroundMode getBackgroundMode() {
      if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
         return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
      } else {
         return BackgroundMode.opaque;
      }
   }

   /**
    * Hook for subclasses to easily provide a custom {@link
    * io.flutter.embedding.engine.FlutterEngine}.
    *
    * <p>This hook is where a cached {@link io.flutter.embedding.engine.FlutterEngine} should be
    * provided, if a cached {@link FlutterEngine} is desired.
    */
   @Nullable
   @Override
   public FlutterEngine provideFlutterEngine(@NonNull Context context) {
      // No-op. Hook for subclasses.
      return null;
   }

   /**
    * Hook for subclasses to obtain a reference to the {@link
    * io.flutter.embedding.engine.FlutterEngine} that is owned by this {@code FlutterActivity}.
    *
    * @return The Flutter engine.
    */
   @Nullable
   protected FlutterEngine getFlutterEngine() {
      return delegate.getFlutterEngine();
   }

   /**
    * Retrieves the meta data specified in the AndroidManifest.xml.
    *
    * @return The meta data.
    * @throws PackageManager.NameNotFoundException if a package with the given name cannot be found
    *     on the system.
    */
   @Nullable
   protected Bundle getMetaData() throws PackageManager.NameNotFoundException {
      ActivityInfo activityInfo =
              getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
      return activityInfo.metaData;
   }

   @Nullable
   @Override
   public PlatformPlugin providePlatformPlugin(
           @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
      return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this);
   }

   /**
    * Hook for subclasses to easily configure a {@code FlutterEngine}.
    *
    * <p>This method is called after {@link #provideFlutterEngine(Context)}.
    *
    * <p>All plugins listed in the app's pubspec are registered in the base implementation of this
    * method unless the FlutterEngine for this activity was externally created. To avoid the
    * automatic plugin registration for implicitly created FlutterEngines, override this method
    * without invoking super(). To keep automatic plugin registration and further configure the
    * FlutterEngine, override this method, invoke super(), and then configure the FlutterEngine as
    * desired.
    */
   @Override
   public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
      if (delegate.isFlutterEngineFromHost()) {
         // If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
         // builder should explicitly decide whether to automatically register plugins via the
         // FlutterEngine's construction parameter or via the AndroidManifest metadata.
         return;
      }

      GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
   }

   /**
    * Hook for the host to cleanup references that were established in {@link
    * #configureFlutterEngine(FlutterEngine)} before the host is destroyed or detached.
    *
    * <p>This method is called in {@link #onDestroy()}.
    */
   @Override
   public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
      // No-op. Hook for subclasses.
   }

   /**
    * Hook for subclasses to control whether or not the {@link FlutterFragment} within this {@code
    * Activity} automatically attaches its {@link io.flutter.embedding.engine.FlutterEngine} to this
    * {@code Activity}.
    *
    * <p>This property is controlled with a protected method instead of an {@code Intent} argument
    * because the only situation where changing this value would help, is a situation in which {@code
    * FlutterActivity} is being subclassed to utilize a custom and/or cached {@link
    * io.flutter.embedding.engine.FlutterEngine}.
    *
    * <p>Defaults to {@code true}.
    *
    * <p>Control surfaces are used to provide Android resources and lifecycle events to plugins that
    * are attached to the {@link io.flutter.embedding.engine.FlutterEngine}. If {@code
    * shouldAttachEngineToActivity} is true, then this {@code FlutterActivity} will connect its
    * {@link io.flutter.embedding.engine.FlutterEngine} to itself, along with any plugins that are
    * registered with that {@link io.flutter.embedding.engine.FlutterEngine}. This allows plugins to
    * access the {@code Activity}, as well as receive {@code Activity}-specific calls, e.g. {@link
    * Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false, then this
    * {@code FlutterActivity} will not automatically manage the connection between its {@link
    * FlutterEngine} and itself. In this case, plugins will not be offered a reference to an {@code
    * Activity} or its OS hooks.
    *
    * <p>Returning false from this method does not preclude a {@link
    * io.flutter.embedding.engine.FlutterEngine} from being attaching to a {@code FlutterActivity} -
    * it just prevents the attachment from happening automatically. A developer can choose to
    * subclass {@code FlutterActivity} and then invoke {@link
    * ActivityControlSurface#attachToActivity(ExclusiveAppComponent, Lifecycle)} and {@link
    * ActivityControlSurface#detachFromActivity()} at the desired times.
    *
    * <p>One reason that a developer might choose to manually manage the relationship between the
    * {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the developer
    * wants to move the {@link FlutterEngine} somewhere else. For example, a developer might want the
    * {@link io.flutter.embedding.engine.FlutterEngine} to outlive this {@code FlutterActivity} so
    * that it can be used later in a different {@code Activity}. To accomplish this, the {@link
    * io.flutter.embedding.engine.FlutterEngine} may need to be disconnected from this {@code
    * FlutterActivity} at an unusual time, preventing this {@code FlutterActivity} from correctly
    * managing the relationship between the {@link io.flutter.embedding.engine.FlutterEngine} and
    * itself.
    */
   @Override
   public boolean shouldAttachEngineToActivity() {
      return true;
   }

   /**
    * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
    * getInitialRoute} returns null.
    *
    * <p>The default implementation looks {@code <meta-data>} called {@link
    * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest
    * definition for this {@code FlutterActivity}.
    */
   @Override
   public boolean shouldHandleDeeplinking() {
      try {
         Bundle metaData = getMetaData();
         boolean shouldHandleDeeplinking =
                 metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false;
         return shouldHandleDeeplinking;
      } catch (PackageManager.NameNotFoundException e) {
         return false;
      }
   }

   @Override
   public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {
      // Hook for subclasses.
   }

   @Override
   public void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView) {
      // Hook for subclasses.
   }

   @Override
   public void onFlutterUiDisplayed() {
      // Notifies Android that we're fully drawn so that performance metrics can be collected by
      // Flutter performance tests. A few considerations:
      // * reportFullyDrawn was supported in KitKat (API 19), but has a bug around requiring
      // permissions in some Android versions.
      // * reportFullyDrawn behavior isn't tested on pre-Q versions.
      // See https://github.com/flutter/flutter/issues/46172, and
      // https://github.com/flutter/flutter/issues/88767.
      if (Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
         reportFullyDrawn();
      }
   }

   @Override
   public void onFlutterUiNoLongerDisplayed() {
      // no-op
   }

   @Override
   public boolean shouldRestoreAndSaveState() {
      if (getIntent().hasExtra(EXTRA_ENABLE_STATE_RESTORATION)) {
         return getIntent().getBooleanExtra(EXTRA_ENABLE_STATE_RESTORATION, false);
      }
      if (getCachedEngineId() != null) {
         // Prevent overwriting the existing state in a cached engine with restoration state.
         return false;
      }
      return true;
   }

   /**
    * Give the host application a chance to take control of the app lifecycle events.
    *
    * <p>Return {@code false} means the host application dispatches these app lifecycle events, while
    * return {@code true} means the engine dispatches these events.
    *
    * <p>Defaults to {@code true}.
    */
   @Override
   public boolean shouldDispatchAppLifecycleState() {
      return true;
   }

   /**
    * Whether to automatically attach the {@link FlutterView} to the engine.
    *
    * <p>Returning {@code false} means that the task of attaching the {@link FlutterView} to the
    * engine will be taken over by the host application.
    *
    * <p>Defaults to {@code true}.
    */
   @Override
   public boolean attachToEngineAutomatically() {
      return true;
   }

   @Override
   public boolean popSystemNavigator() {
      // Hook for subclass. No-op if returns false.
      return false;
   }

   @Override
   public void updateSystemUiOverlays() {
      if (delegate != null) {
         delegate.updateSystemUiOverlays();
      }
   }

   private boolean stillAttachedForEvent(String event) {
      if (delegate == null) {
         Log.w(TAG, "FlutterActivity " + hashCode() + " " + event + " called after release.");
         return false;
      }
      if (!delegate.isAttached()) {
         Log.w(TAG, "FlutterActivity " + hashCode() + " " + event + " called after detach.");
         return false;
      }
      return true;
   }
}
